/**
	This file is part of Fighting Field.

    Fighting Field is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Fighting Field is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Fighting Field.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef FF_GUIMANAGER_H
#define FF_GUIMANAGER_H

#include "Prerequisites.h"

#include "Button.h"
#include "CheckBox.h"
#include "DecorWidget.h"
#include "GuiListener.h"
#include "InputListener.h"
#include "Label.h"
#include "ParamsPanel.h"
#include "ProgressBar.h"
#include "SelectMenu.h"
#include "Separator.h"
#include "Slider.h"
#include "TextBox.h"
#include "Widget.h"

namespace FF
{
	class GuiManager
		: public GuiListener
		, public InputListener
		, public Ogre::ResourceGroupListener
	{
    public:
		GuiManager(const Ogre::String& name, Ogre::RenderWindow* window, OIS::Mouse* mouse, GuiListener* listener = 0);
		virtual ~GuiManager();

		// init basics widgets
		void InitCursor(const Ogre::String& templateName);
		void InitDialogShade(const Ogre::String& materialName);
		void InitTrays(const Ogre::String& templateName);

		// Converts a 2D screen coordinate (in pixels) to a 3D ray into the scene.
		static Ogre::Ray ScreenToScene(Ogre::Camera* cam, const Ogre::Vector2& pt);
		// Converts a 3D scene position to a 2D screen coordinate (in pixels).
		static Ogre::Vector2 SceneToScreen(Ogre::Camera* cam, const Ogre::Vector3& pt);

		// these methods get the underlying overlays and overlay elements
		Ogre::OverlayContainer* GetTrayContainer(TrayLocation::Val trayLoc) { return mTrays[trayLoc]; }
		Ogre::Overlay* GetBackdropLayer() { return mBackdropLayer; }
		Ogre::Overlay* GetTraysLayer() { return mTraysLayer; }
		Ogre::Overlay* GetCursorLayer() { return mCursorLayer; }
		Ogre::OverlayContainer* GetBackdropContainer() { return mBackdrop; }
		Ogre::OverlayContainer* GetCursorContainer() { return mCursor; }
		Ogre::OverlayElement* GetCursorImage() { return mCursor->getChild(mCursor->getName() + "/CursorImage"); }

		void SetListener(GuiListener* listener);
		GuiListener* GetListener();

		void ShowAll();
		void HideAll();

		// Displays specified material on backdrop, or the last material used if
		// none specified. Good for pause menus like in the browser.
		void ShowBackdrop(const Ogre::String& materialName = Ogre::StringUtil::BLANK);
		void HideBackdrop();

		// Displays specified material on cursor, or the last material used if
		// none specified. Used to change cursor type.
		void ShowCursor(const Ogre::String& materialName = Ogre::StringUtil::BLANK);
		void HideCursor();
		// Updates cursor position based on unbuffered mouse state. This is necessary
		// because if the tray manager has been cut off from mouse events for a time,
		// the cursor position will be out of date.
		void RefreshCursor();

		void ShowTrays();
		void HideTrays();

		bool IsCursorVisible() { return mCursorLayer->isVisible(); }
		bool IsBackdropVisible() { return mBackdropLayer->isVisible(); }
		bool AreTraysVisible() { return mTraysLayer->isVisible(); }

		// Sets horizontal alignment of a tray's contents.
		void SetTrayWidgetAlignment(TrayLocation::Val trayLoc, Ogre::GuiHorizontalAlignment gha);
		// padding and spacing methods
		void SetWidgetPadding(Ogre::Real padding);
		void SetWidgetSpacing(Ogre::Real spacing);
		void SetTrayPadding(Ogre::Real padding);

		virtual Ogre::Real GetWidgetPadding() const { return mWidgetPadding; }
		virtual Ogre::Real GetWidgetSpacing() const { return mWidgetSpacing; }
		virtual Ogre::Real GetTrayPadding() const { return mTrayPadding; }

		// Fits trays to their contents and snaps them to their anchor locations.
		virtual void AdjustTrays();

		// Returns a 3D ray into the scene that is directly underneath the cursor.
		Ogre::Ray GetCursorRay(Ogre::Camera* cam);

		Button* CreateButton(TrayLocation::Val trayLoc, const Ogre::String& name, const Button::Style& style, const Ogre::String& caption, Ogre::Real width = 0);
		
		TextBox* CreateTextBox(TrayLocation::Val trayLoc, const Ogre::String& name, const TextBox::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width, Ogre::Real height);
		
		SelectMenu* CreateThickSelectMenu(TrayLocation::Val trayLoc, const Ogre::String& name, const SelectMenu::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width, unsigned int maxItemsShown, const Ogre::StringVector& items = Ogre::StringVector());
		SelectMenu* CreateLongSelectMenu(TrayLocation::Val trayLoc, const Ogre::String& name, const SelectMenu::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width, Ogre::Real boxWidth, unsigned int maxItemsShown, const Ogre::StringVector& items = Ogre::StringVector());
		SelectMenu* CreateLongSelectMenu(TrayLocation::Val trayLoc, const Ogre::String& name, const SelectMenu::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real boxWidth, unsigned int maxItemsShown, const Ogre::StringVector& items = Ogre::StringVector());
		
		Label* CreateLabel(TrayLocation::Val trayLoc, const Ogre::String& name, const Label::Style& style, const Ogre::DisplayString& caption, Ogre::Real width = 0);
		
		Separator* CreateSeparator(TrayLocation::Val trayLoc, const Ogre::String& name, const Separator::Style& style, Ogre::Real width = 0);
		
		Slider* CreateThickSlider(TrayLocation::Val trayLoc, const Ogre::String& name, const Slider::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width, Ogre::Real valueBoxWidth, Ogre::Real minValue, Ogre::Real maxValue, unsigned int snaps);
		Slider* CreateLongSlider(TrayLocation::Val trayLoc, const Ogre::String& name, const Slider::Style& style, const Ogre::DisplayString& caption, Ogre::Real width,
			Ogre::Real trackWidth, Ogre::Real valueBoxWidth, Ogre::Real minValue, Ogre::Real maxValue, unsigned int snaps);
		Slider* CreateLongSlider(TrayLocation::Val trayLoc, const Ogre::String& name, const Slider::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real trackWidth, Ogre::Real valueBoxWidth, Ogre::Real minValue, Ogre::Real maxValue, unsigned int snaps);
		
		ParamsPanel* CreateParamsPanel(TrayLocation::Val trayLoc, const Ogre::String& name, const ParamsPanel::Style& style, Ogre::Real width, unsigned int lines);
		ParamsPanel* CreateParamsPanel(TrayLocation::Val trayLoc, const Ogre::String& name, const ParamsPanel::Style& style, Ogre::Real width,
			const Ogre::StringVector& paramNames);
		
		CheckBox* CreateCheckBox(TrayLocation::Val trayLoc, const Ogre::String& name, const CheckBox::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width = 0);
		
		DecorWidget* CreateDecorWidget(TrayLocation::Val trayLoc, const Ogre::String& name, const Ogre::String& templateName);
		
		ProgressBar* CreateProgressBar(TrayLocation::Val trayLoc, const Ogre::String& name, const ProgressBar::Style& style, const Ogre::DisplayString& caption,
			Ogre::Real width, Ogre::Real commentBoxWidth);

		// Shows frame statistics widget set in the specified location.
		void ShowFrameStats(TrayLocation::Val trayLoc, int place = -1);
		// Hides frame statistics widget set.
		void HideFrameStats();
		bool AreFrameStatsVisible();
		// Toggles visibility of advanced statistics.
		void ToggleAdvancedFrameStats();

		// Shows logo in the specified location.
		void ShowLogo(TrayLocation::Val trayLoc, int place = -1);
		void HideLogo();
		bool IsLogoVisible();

		// Shows loading bar. Also takes job settings: the number of resource groups
		// to be initialised, the number of resource groups to be loaded, and the
		// proportion of the job that will be taken up by initialisation. Usually,
		// script parsing takes up most time, so the default value is 0.7.
		void ShowLoadingBar(unsigned int numGroupsInit = 1, unsigned int numGroupsLoad = 1,
			Ogre::Real initProportion = 0.7);
		void HideLoadingBar();
		bool IsLoadingBarVisible();

		// Pops up a message dialog with an OK button.
		void ShowOkDialog(const Ogre::DisplayString& caption, const Ogre::DisplayString& message);
		// Pops up a question dialog with Yes and No buttons.
		void ShowYesNoDialog(const Ogre::DisplayString& caption, const Ogre::DisplayString& question);
		// Hides whatever dialog is currently showing.
		void CloseDialog();
		// Determines if any dialog is currently visible.
		bool IsDialogVisible();

		// Gets a widget from a tray by place.
		Widget* GetWidget(TrayLocation::Val trayLoc, unsigned int place);
		// Gets a widget from a tray by name.
		Widget* GetWidget(TrayLocation::Val trayLoc, const Ogre::String& name);
		// Gets a widget by name.
		Widget* GetWidget(const Ogre::String& name);
		
		// Gets the number of widgets in total.
		unsigned int GetNumWidgets();
		// Gets the number of widgets in a tray.
		unsigned int GetNumWidgets(TrayLocation::Val trayLoc);
		
		// Gets all the widgets of a specific tray.
		WidgetIterator GetWidgetIterator(TrayLocation::Val trayLoc);
		
		// Gets a widget's position in its tray.
		int LocateWidgetInTray(Widget* widget);

		// Destroys a widget.
		void DestroyWidget(Widget* widget);
		void DestroyWidget(TrayLocation::Val trayLoc, unsigned int place);
		void DestroyWidget(TrayLocation::Val trayLoc, const Ogre::String& name);
		void DestroyWidget(const Ogre::String& name);
		// Destroys all widgets in a tray.
		void DestroyAllWidgetsInTray(TrayLocation::Val trayLoc);
		// Destroys all widgets.
		void DestroyAllWidgets();

		// Adds a widget to a specified tray.
		void MoveWidgetToTray(Widget* widget, TrayLocation::Val trayLoc, int place = -1);
		void MoveWidgetToTray(const Ogre::String& name, TrayLocation::Val trayLoc, unsigned int place = -1);
		void MoveWidgetToTray(TrayLocation::Val currentTrayLoc, const Ogre::String& name, TrayLocation::Val targetTrayLoc,
			int place = -1);
		void MoveWidgetToTray(TrayLocation::Val currentTrayLoc, unsigned int currentPlace, TrayLocation::Val targetTrayLoc,
			int targetPlace = -1);

		// Removes a widget from its tray. Same as moving it to the null tray.
		void RemoveWidgetFromTray(Widget* widget);
		void RemoveWidgetFromTray(const Ogre::String& name);
		void RemoveWidgetFromTray(TrayLocation::Val trayLoc, const Ogre::String& name);
		void RemoveWidgetFromTray(TrayLocation::Val trayLoc, int place);
		
		// Removes all widgets from a widget tray.
		void ClearTray(TrayLocation::Val trayLoc);
		// Removes all widgets from all widget trays.
		void ClearAllTrays();

		// Process frame events. Updates frame statistics widget set and deletes
		// all widgets queued for destruction.
		bool Update(const Ogre::Real& dt);

		void resourceGroupScriptingStarted(const Ogre::String& groupName, size_t scriptCount);
		void scriptParseStarted(const Ogre::String& scriptName, bool& skipThisScript);
		void scriptParseEnded(const Ogre::String& scriptName, bool skipped);
		void resourceGroupScriptingEnded(const Ogre::String& groupName) {}
		void resourceGroupLoadStarted(const Ogre::String& groupName, size_t resourceCount);

		void resourceLoadStarted(const Ogre::ResourcePtr& resource);
		void resourceLoadEnded();

		void worldGeometryStageStarted(const Ogre::String& description);
		void worldGeometryStageEnded();

		void resourceGroupLoadEnded(const Ogre::String& groupName) {}

		// Toggles visibility of advanced statistics.
		void OnLabelHit(Label* label);

		// Destroys dialog widgets, notifies listener, and ends high priority session.
		void OnButtonHit(Button* button);

		// Processes mouse button down events. Returns true if the event was
		// consumed and should not be passed on to other handlers.
		bool OnMousePressed(const OIS::MouseEvent& evt, OIS::MouseButtonID id);

		// Processes mouse button up events. Returns true if the event was
		// consumed and should not be passed on to other handlers.
		bool OnMouseReleased(const OIS::MouseEvent& evt, OIS::MouseButtonID id);

		// Updates cursor position. Returns true if the event was
		// consumed and should not be passed on to other handlers.
		bool OnMouseMoved(const OIS::MouseEvent& evt);

    protected:
		// Internal method to prioritise / deprioritise expanded menus.
		void SetExpandedMenu(SelectMenu* m);

    protected:
		Ogre::String mName;                   // name of this tray system
		Ogre::RenderWindow* mWindow;          // render window
		OIS::Mouse* mMouse;                   // mouse device
		Ogre::Overlay* mBackdropLayer;        // backdrop layer
		Ogre::Overlay* mTraysLayer;           // widget layer
		Ogre::Overlay* mPriorityLayer;        // top priority layer
		Ogre::Overlay* mCursorLayer;          // cursor layer
		Ogre::OverlayContainer* mBackdrop;    // backdrop
		Ogre::OverlayContainer* mTrays[10];   // widget trays
	    WidgetList mWidgets[10];              // widgets
		WidgetList mWidgetDeathRow;           // widget queue for deletion
		Ogre::OverlayContainer* mCursor;      // cursor
		GuiListener* mListener;           // tray listener
		Ogre::Real mWidgetPadding;            // widget padding
		Ogre::Real mWidgetSpacing;            // widget spacing
		Ogre::Real mTrayPadding;              // tray padding
		bool mTrayDrag;                       // a mouse press was initiated on a tray
		SelectMenu* mExpandedMenu;            // top priority expanded menu widget
		TextBox* mDialog;                     // top priority dialog widget
		Ogre::OverlayContainer* mDialogShade; // top priority dialog shade
		Button* mOk;                          // top priority OK button
		Button* mYes;                         // top priority Yes button
		Button* mNo;                          // top priority No button
		bool mCursorWasVisible;               // cursor state before showing dialog
		Label* mFpsLabel;                     // FPS label
		ParamsPanel* mStatsPanel;             // frame stats panel
		DecorWidget* mLogo;                   // logo
		ProgressBar* mLoadBar;                // loading bar
		Ogre::Real mGroupInitProportion;      // proportion of load job assigned to initialising one resource group
		Ogre::Real mGroupLoadProportion;      // proportion of load job assigned to loading one resource group
		Ogre::Real mLoadInc;                  // loading increment
		Ogre::GuiHorizontalAlignment mTrayWidgetAlign[10];   // tray widget alignments
    };
}

#endif